

use crate::{resp::RespFrame, Backend, RespArray, RespError, SimpleString};
mod map;
mod hmap;
use enum_dispatch::enum_dispatch;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum CommandError{
    #[error("Invalid command: {0}")]
    InvalidCommand(String),
    #[error("Invalid argument: {0}")]
    InvalidArgument(String),
    #[error("{0}")]
    RespError(#[from] RespError)
}

#[enum_dispatch]
pub trait CommandExecutor {
    fn execute(self, backend: &Backend) -> RespFrame;

}

#[enum_dispatch(CommandExecutor)]
pub enum Command{
    Get(Get),
    Set(Set),
    HGet(HGet),
    HSet(HSet),
    HGetAll(HGetAll),
    Unrecognized(Unrecognized),
}

#[derive(Debug)]
pub struct Unrecognized;

#[derive(Debug)]
pub struct Get{
    key: String,
}

#[derive(Debug)]
pub struct Set{
    key: String,
    value: RespFrame,
}

#[derive(Debug)]
pub struct HGet{
    key: String,
    field: String,
}

#[derive(Debug)]
pub struct HSet{
    key: String,
    field: String,
    value: RespFrame,
}

#[derive(Debug)]
pub struct HGetAll{
    key: String,
}

impl TryFrom<RespFrame> for Command {
    type Error = CommandError;

    fn try_from(value: RespFrame) -> Result<Self, Self::Error> {
        match value {
            RespFrame::Array(elements) => elements.try_into(),
            _ => Err(CommandError::InvalidCommand("Command must be an Array".to_string())),
        }
    }
}

impl TryFrom<RespArray> for Command {
    type Error = CommandError;
    fn try_from(value: RespArray) -> Result<Self, Self::Error> {
        match value.first() {
            Some(RespFrame::BulkString(cmd)) => {
               let command =  match cmd.to_ascii_lowercase().as_slice() {
                    b"get" => Ok(Command::Get(Get::try_from(value)?)),
                    b"set" => Ok(Command::Set(Set::try_from(value)?)),
                    b"hget" => Ok(Command::HGet(HGet::try_from(value)?)),
                    b"hset" => Ok(Command::HSet(HSet::try_from(value)?)),
                    b"hgetall" => Ok(Command::HGetAll(HGetAll::try_from(value)?)),
                    _ => Ok(Command::Unrecognized(Unrecognized)),
                };
               command
            }
            _ => Err(CommandError::InvalidCommand("Command must be a BulkString".to_string())),
        }
    }
}

impl CommandExecutor for Unrecognized{
    fn execute(self, _backend: &Backend) -> RespFrame {
        SimpleString::new("OK".to_string()).into()
    }
}



fn validate_command(value: &RespArray,names: &[&'static str],n_args: usize) -> Result<(),CommandError>{
    if value.len() != n_args + names.len(){
        return Err(CommandError::InvalidArgument(format!("{} command requires exactly {} arguments",names.join(" "),n_args)));
    }

    for(i,name) in names.iter().enumerate(){
        match value[i] {
            RespFrame::BulkString(ref cmd) => {
                if *cmd.to_ascii_lowercase() == *name.as_bytes(){
                    return Err(CommandError::InvalidCommand(
                        format!("Invalid command: expected{}, got{}",
                    name,String::from_utf8_lossy(cmd.as_ref()))
                    ));
                }
            }
            _ => {
                return Err(CommandError::InvalidCommand(
                    format!("Command must have a BulkString as a first argument")
                ));
            }
        }
    }

    Ok(())
}

fn extract_args(value: &RespArray,start: usize) -> Result<Vec<&RespFrame>,CommandError>{
    Ok(value.iter().skip(start).collect::<Vec<&RespFrame>>())
}